package kz.pompei.ifacer.grot;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import javax.swing.JColorChooser;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.filechooser.FileFilter;

import kz.pompei.ifacer.cursor_manager.CursorManager;
import kz.pompei.ifacer.dialogs.ButtonDialog;
import kz.pompei.ifacer.dialogs.FontChooserDialog;
import kz.pompei.ifacer.dialogs.FontUtil;
import kz.pompei.ifacer.grot.acts.Act;
import kz.pompei.ifacer.grot.acts.ActContainer;
import kz.pompei.ifacer.grot.acts.ActGroup;
import kz.pompei.ifacer.grot.acts.AddAct;
import kz.pompei.ifacer.grot.acts.ChangeFieldsAct;
import kz.pompei.ifacer.grot.acts.DeleteAct;
import kz.pompei.ifacer.model.CursorInfo;
import kz.pompei.ifacer.model.CursorKind;
import kz.pompei.ifacer.model.PaintKind;
import kz.pompei.ifacer.model.widgets.Widget;
import kz.pompei.ifacer.model.widgets.WidgetRect;
import kz.pompei.ifacer.util.Handler;
import kz.pompei.ifacer.util.Util;
import kz.pompei.ifacer.visio.Visio;

public class GrotPanel extends JPanel {
  
  private static final int MOVE_START_DIST2 = 9;
  
  public File workingFile;
  public final ActContainer actContainer;
  public final Visio visio;
  private final CursorManager cursorManager;
  public final Align align;
  
  public GrotPanel(ActContainer actContainer, Visio visio, CursorManager cursorManager) {
    this.actContainer = actContainer;
    this.visio = visio;
    this.cursorManager = cursorManager;
    align = new Align(this);
    
    addMouseMotionListener(new MouseMotionListener() {
      @Override
      public void mouseMoved(MouseEvent e) {
        doMouseMoved(e);
      }
      
      @Override
      public void mouseDragged(MouseEvent e) {
        try {
          doMouseDragged(e);
        } catch (Exception e1) {
          throw new RuntimeException(e1);
        }
      }
    });
    
    addMouseListener(new MouseListener() {
      @Override
      public void mouseReleased(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON1) {
          try {
            leftButtonReleased(e);
          } catch (Exception e1) {
            throw new RuntimeException(e1);
          }
        }
        if (e.getButton() == MouseEvent.BUTTON3) {
          rightButtonReleased(e);
        }
      }
      
      private void rightButtonReleased(MouseEvent e) {}
      
      @Override
      public void mousePressed(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON1) {
          try {
            leftButtonPressed(e);
          } catch (Exception e1) {
            throw new RuntimeException(e1);
          }
        }
      }
      
      @Override
      public void mouseExited(MouseEvent e) {
        mouse = e.getPoint();
        repaint(100);
      }
      
      @Override
      public void mouseEntered(MouseEvent e) {
        mouse = e.getPoint();
        repaint(100);
      }
      
      @Override
      public void mouseClicked(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) {
          try {
            rightButtonClicked(e);
          } catch (Exception e1) {
            throw new RuntimeException(e1);
          }
        }
      }
      
      private void rightButtonClicked(MouseEvent e) throws Exception {
        popup.update();
        popup.show(e.getComponent(), e.getX(), e.getY());
      }
    });
    
    actContainer.updates.add(new Handler() {
      @Override
      public void handle() throws Exception {
        repaint(100);
      }
    });
  }
  
  private void resetCursor() {
    try {
      resetCursorInner();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  private class PointInfo {
    final Widget widget;
    final CursorInfo cursorInfo;
    
    public PointInfo(Widget widget, CursorInfo cursorInfo) {
      this.widget = widget;
      this.cursorInfo = cursorInfo;
    }
  }
  
  private PointInfo getPointInfo(Point point) throws Exception {
    Graphics2D g2 = newG();
    
    for (int i = actContainer.storage.size(); i-- > 0;) {
      Widget widget = actContainer.storage.get(i);
      
      if (!widget.active) continue;
      
      CursorInfo cursorInfo = visio.getCursorInfo(g2, widget, point);
      
      if (cursorInfo != null) {
        g2.dispose();
        return new PointInfo(widget, cursorInfo);
      }
    }
    
    g2.dispose();
    return null;
  }
  
  private void resetCursorInner() throws Exception {
    if (moving != null) {
      setCursor(cursorManager.getCursor(null));
      return;
    }
    
    PointInfo pointInfo = getPointInfo(mouse);
    if (pointInfo == null) {
      setCursor(cursorManager.getCursor(new CursorInfo(CursorKind.DEF, null)));
    } else {
      setCursor(cursorManager.getCursor(pointInfo.cursorInfo));
    }
  }
  
  @Override
  public void paint(Graphics g1) {
    Graphics2D g = (Graphics2D)g1;
    
    try {
      paintInner(g);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  public final Set<UUID> selectedUuids = new HashSet<>();
  
  public int selSize() {
    return selectedUuids.size();
  }
  
  private void paintInner(Graphics2D g) throws Exception {
    g.setClip(0, 0, getWidth(), getHeight());
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, getWidth(), getHeight());
    
    {
      Graphics2D g2 = (Graphics2D)g.create();
      paintPre(g2);
      g2.dispose();
    }
    if (moving != null) {
      moving.startPaint(g);
    }
    
    for (Widget widget : actContainer.storage) {
      if (moving != null && moving.skipPaint(widget)) continue;
      
      boolean over = widget.active ? visio.getCursorInfo(g, widget, mouse) != null :false;
      boolean selected = selectedUuids.contains(widget.uuid);
      if (!selected && moving != null) {
        selected = selected || moving.needToSelect(g, widget);
      }
      visio.paint(g, widget, PaintKind.byState(over, selected), widget.active ? mouse :null,
          allSpace());
    }
    
    if (moving != null) {
      moving.paint(g);
    }
  }
  
  private Rectangle allSpace() {
    return new Rectangle(0, 0, getWidth(), getHeight());
  }
  
  private abstract class Moving {
    abstract boolean needToSelect(Graphics2D g, Widget widget) throws Exception;
    
    public boolean skipPaint(Widget widget) {
      return false;
    }
    
    abstract void startPaint(Graphics2D g);
    
    abstract void paint(Graphics2D g) throws Exception;
    
    abstract void commit(MouseEvent e) throws Exception;
  }
  
  private class MovingSelect extends Moving {
    final Set<UUID> selected = new HashSet<>();
    
    @Override
    void startPaint(Graphics2D g) {
      selected.clear();
    }
    
    @Override
    boolean needToSelect(Graphics2D g, Widget widget) throws Exception {
      if (widget.active == false) return false;
      boolean ret = check(g, widget);
      if (ret) selected.add(widget.uuid);
      return ret;
    }
    
    @Override
    void commit(MouseEvent e) {
      if (!e.isControlDown()) {
        selectedUuids.clear();
      }
      selectedUuids.addAll(selected);
    }
    
    private boolean check(Graphics2D g, Widget widget) throws Exception {
      if (mousePressedAt == null) return false;
      Rectangle rect = visio.getWidgetBound(g, widget);
      boolean full = mousePressedAt.x > mouse.x;//selecting like in AutoCAD
      Rectangle selRect = Util.rectBy2Points(mousePressedAt, mouse);
      if (full) return selRect.contains(rect);
      return selRect.intersects(rect);
    }
    
    @Override
    public void paint(Graphics2D g) {
      if (mousePressedAt == null) return;
      g.setColor(Color.GRAY);
      boolean full = mousePressedAt.x > mouse.x;//selecting like in AutoCAD
      Rectangle r = Util.rectBy2Points(mousePressedAt, mouse);
      
      if (!full) {
        float dash1[] = { 3f, 3f };
        BasicStroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
            10.0f, dash1, 0.0f);
        
        g.setStroke(dashed);
      }
      
      g.drawRect(r.x, r.y, r.width, r.height);
    }
  }
  
  class MovingManyWidgets extends Moving {
    
    class Pair {
      Widget original, copy;
      
      public Pair(Widget original, Widget copy) {
        this.original = original;
        this.copy = copy;
      }
    }
    
    final Set<Pair> pairs = new HashSet<>();
    
    public MovingManyWidgets() {
      for (UUID uuid : selectedUuids) {
        Widget original = actContainer.storage.getWidgetByUuid(uuid);
        pairs.add(new Pair(original, original.copy()));
      }
    }
    
    @Override
    public boolean skipPaint(Widget widget) {
      return selectedUuids.contains(widget.uuid);
    }
    
    @Override
    boolean needToSelect(Graphics2D g, Widget widget) throws Exception {
      return false;
    }
    
    @Override
    void startPaint(Graphics2D g) {}
    
    @Override
    void paint(Graphics2D g) throws Exception {
      for (Pair pair : pairs) {
        pair.copy.takePosition(pair.original);
        pair.copy.move(mouse.x - mousePressedAt.x, mouse.y - mousePressedAt.y);
        visio.paint(g, pair.copy, PaintKind.MOVING, mouse, allSpace());
      }
    }
    
    @Override
    void commit(MouseEvent e) throws Exception {
      ActGroup group = new ActGroup();
      for (Pair pair : pairs) {
        pair.copy.takePosition(pair.original);
        pair.copy.move(mouse.x - mousePressedAt.x, mouse.y - mousePressedAt.y);
        group.group.add(new ChangeFieldsAct(pair.copy.uuid, "x", pair.copy.x, "y", pair.copy.y));
      }
      actContainer.addAct(group);
    }
  }
  
  class MovingSingleWidget extends Moving {
    
    private Widget original, copy;
    
    public MovingSingleWidget(PointInfo startedPointInfo) {
      original = startedPointInfo.widget;
      copy = original.copy();
    }
    
    @Override
    public boolean skipPaint(Widget widget) {
      return original.uuid.equals(widget.uuid);
    }
    
    @Override
    boolean needToSelect(Graphics2D g, Widget widget) throws Exception {
      return false;
    }
    
    @Override
    void startPaint(Graphics2D g) {}
    
    private Act lastAct;
    
    @Override
    void paint(Graphics2D g) throws Exception {
      lastAct = visio.move(g, original, copy, mousePressedAt, mouse);
      visio.paint(g, copy, PaintKind.MOVING, mouse, allSpace());
    }
    
    @Override
    void commit(MouseEvent e) throws Exception {
      actContainer.addAct(lastAct);
    }
  }
  
  class MovingAdd extends Moving {
    
    private Widget original, copy;
    
    public MovingAdd(Widget original) {
      this.original = original;
      original.x = original.y = 0;
      copy = original.copy();
    }
    
    @Override
    boolean needToSelect(Graphics2D g, Widget widget) throws Exception {
      return false;
    }
    
    @Override
    void startPaint(Graphics2D g) {}
    
    @Override
    void paint(Graphics2D g) throws Exception {
      copy.takePosition(original);
      if (mousePressedAt == null || Util.sqr(mousePressedAt, mouse) < MOVE_START_DIST2) {
        copy.move(mouse.x, mouse.y);
      } else {
        if (copy instanceof WidgetRect) {
          WidgetRect wr = (WidgetRect)copy;
          Rectangle r = Util.rectBy2Points(mousePressedAt, mouse);
          wr.x = r.x;
          wr.y = r.y;
          wr.width = r.width;
          wr.height = r.height;
        } else {
          copy.move(mousePressedAt.x, mousePressedAt.y);
        }
      }
      visio.paint(g, copy, PaintKind.MOVING, mouse, allSpace());
    }
    
    @Override
    void commit(MouseEvent e) throws Exception {
      actContainer.addAct(new AddAct(copy));
    }
  }
  
  public void addWidget(Widget widget) {
    moving = new MovingAdd(widget);
    repaint(100);
  }
  
  private Point mouse = new Point(0, 0);
  private Point mousePressedAt = null;
  private Moving moving = null;
  
  private void leftButtonPressed(MouseEvent e) throws Exception {
    if (e.getClickCount() == 1) {//mouse single clicked
      mouse = e.getPoint();
      mousePressedAt = mouse;
      repaint(100);
      return;
    }
    if (e.getClickCount() == 2) {//mouse double clicked
      PointInfo pi = getPointInfo(mouse);
      if (pi == null) return;
      Graphics2D g = newG();
      Act act = visio.getDoubleClickAct(g, pi.widget, mouse, this);
      if (act == null) return;
      actContainer.addAct(act);
      repaint(100);
      return;
    }
  }
  
  private void leftButtonReleased(MouseEvent e) throws Exception {
    mouse = e.getPoint();
    
    final Set<UUID> oldSel = new HashSet<>();
    oldSel.addAll(selectedUuids);
    
    if (moving == null && mousePressedAt != null) {
      if (!e.isControlDown()) {
        selectedUuids.clear();
      }
      
      PointInfo pi = getPointInfo(mouse);
      if (pi != null) {
        if (e.isControlDown() && selectedUuids.contains(pi.widget.uuid)) {
          selectedUuids.remove(pi.widget.uuid);
        } else {
          selectedUuids.add(pi.widget.uuid);
        }
      }
    }
    
    if (moving != null) {
      moving.commit(e);
      moving = null;
    }
    
    mousePressedAt = null;
    
    repaint(100);
    resetCursor();
    
    if (!oldSel.equals(selectedUuids)) {
      actContainer.updates.fire();
    }
  }
  
  private void doMouseDragged(MouseEvent e) throws Exception {
    if (mousePressedAt == null) return;
    mouse = e.getPoint();
    
    if (moving == null) {
      if (Util.sqr(mouse, mousePressedAt) < MOVE_START_DIST2) return;
      
      if (e.isControlDown()) {
        moving = new MovingSelect();
      } else {
        PointInfo startedPointInfo = getPointInfo(mousePressedAt);
        if (startedPointInfo == null) {
          moving = new MovingSelect();
        } else {
          if (selectedUuids.size() > 1 && selectedUuids.contains(startedPointInfo.widget.uuid)) {
            moving = new MovingManyWidgets();
          } else {
            moving = new MovingSingleWidget(startedPointInfo);
          }
        }
      }
      resetCursorInner();
    }
    
    repaint(100);
  }
  
  private void doMouseMoved(MouseEvent e) {
    mouse = e.getPoint();
    repaint(100);
    resetCursor();
  }
  
  public void selectAll() {
    final Set<UUID> oldSel = new HashSet<>();
    oldSel.addAll(selectedUuids);
    
    for (Widget w : actContainer.storage) {
      if (w.active) selectedUuids.add(w.uuid);
    }
    repaint(100);
    
    if (!oldSel.equals(selectedUuids)) {
      actContainer.updates.fire();
    }
  }
  
  public BufferedImage getExportImage() throws Exception {
    final List<Widget> importWidgets = new ArrayList<>();
    for (Widget w : actContainer.storage) {
      if (selectedUuids.size() == 0 || selectedUuids.contains(w.uuid)) {
        importWidgets.add(w);
      }
    }
    
    if (importWidgets.size() == 0) return null;
    
    BufferedImage test = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    
    Graphics2D testGraphics = (Graphics2D)test.getGraphics();
    
    Rectangle r = visio.getWidgetBound(testGraphics, importWidgets.get(0));
    for (Widget w : importWidgets) {
      r = r.union(visio.getWidgetBound(testGraphics, w));
    }
    
    testGraphics.dispose();
    
    BufferedImage ret = new BufferedImage(r.width + 1, r.height + 1, BufferedImage.TYPE_INT_ARGB);
    
    Graphics2D g = (Graphics2D)ret.getGraphics();
    g.translate(-r.x, -r.y);
    g.setColor(Color.WHITE);
    g.fillRect(r.x, r.y, r.width + 1, r.height + 1);
    
    for (Widget w : importWidgets) {
      visio.paint(g, w, PaintKind.NORMAL, new Point(-1, -1), allSpace());
    }
    
    g.dispose();
    
    return ret;
  }
  
  public void clearSelection() {
    selectedUuids.clear();
    repaint(100);
  }
  
  public void redo() throws Exception {
    actContainer.redo();
    repaint(100);
  }
  
  public void undo() throws Exception {
    actContainer.undo();
    repaint(100);
  }
  
  public void deleteSelected() throws Exception {
    if (selectedUuids.size() == 0) return;
    
    if (selectedUuids.size() == 1) {
      UUID uuid = selectedUuids.iterator().next();
      selectedUuids.clear();
      actContainer.addAct(new DeleteAct(uuid));
      repaint(100);
      return;
    }
    
    ActGroup group = new ActGroup();
    for (UUID uuid : selectedUuids) {
      group.group.add(new DeleteAct(uuid));
    }
    
    selectedUuids.clear();
    actContainer.addAct(group);
    repaint(100);
  }
  
  public void save(OutputStream outStream) throws Exception {
    PrintStream out = new PrintStream(outStream, false, "UTF-8");
    Iterable<Widget> storage = actContainer.storage;
    
    out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    out.println("<ifacer>");
    out.println("  <widgets>");
    for (Widget widget : storage) {
      widget.print(2, out);
    }
    out.println("  </widgets>");
    out.println("</ifacer>");
    out.close();
  }
  
  public void load(FileInputStream inStream) throws Exception {
    actContainer.clean();
    for (Widget widget : FileReader.parseFile(inStream)) {
      actContainer.storage.append(widget);
    }
    repaint(100);
  }
  
  private void paintPre(Graphics2D g) {
    String text1 = "Хлабысь, хлабысь.";
    Font font1 = g.getFont().deriveFont(50f);
    String text2 = "И всё готово!";
    Font font2 = g.getFont().deriveFont(30f);
    
    int height1 = g.getFontMetrics(font1).getHeight();
    int width1 = g.getFontMetrics(font1).stringWidth(text1);
    
    int height2 = g.getFontMetrics(font2).getHeight();
    int width2 = g.getFontMetrics(font2).stringWidth(text2);
    
    int x = getWidth() / 2 - width1 / 2;
    int y = getHeight() / 2 - (height1 + height2) / 2;
    
    g.setColor(new Color(0xF1F1F1));
    g.setFont(font1);
    g.drawString(text1, x, y + height1);
    g.setFont(font2);
    g.drawString(text2, x + width1 - width2, y + height1 + height2);
  }
  
  public List<Widget> selected() {
    List<Integer> indexes = new ArrayList<>();
    for (UUID uuid : selectedUuids) {
      indexes.add(actContainer.storage.getIndexByUuid(uuid));
    }
    
    Collections.sort(indexes);
    
    List<Widget> ret = new ArrayList<>();
    for (Integer index : indexes) {
      ret.add(actContainer.storage.get(index));
    }
    return ret;
  }
  
  public boolean canClose() {
    if (!actContainer.modified()) return true;
    
    {
      ButtonDialog dia = new ButtonDialog(this, 0);
      if (workingFile == null) {
        dia.addMessage("Есть не сохранённые данные");
      } else {
        dia.addMessage("Не сохранён файл: " + workingFile.getAbsolutePath());
      }
      dia.addButton(1, "Сохранить и закрыть");
      dia.addButton(2, "Отказаться от изменений и закрыть");
      dia.addButton(0, "Продолжить редактирование");
      dia.setTitle("Несохранённые данные");
      int result = dia.show();
      if (result == 0) return false;
      if (result == 2) return true;
    }
    
    if (workingFile == null) {
      changeWorkingFile();
      if (workingFile == null) return false;
    }
    
    save();
    
    return true;
  }
  
  private void save() {
    try {
      save(new FileOutputStream(workingFile));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  public boolean changeWorkingFile() {
    JFileChooser fc = new JFileChooser();
    fc.setFileFilter(new FileFilter() {
      @Override
      public String getDescription() {
        return ".iface.xml files";
      }
      
      @Override
      public boolean accept(File f) {
        if (f.isDirectory()) return true;
        return f.getAbsolutePath().endsWith(".iface.xml");
      }
    });
    
    if (JFileChooser.APPROVE_OPTION != fc.showSaveDialog(null)) return false;
    File file = fc.getSelectedFile();
    
    if (!file.getAbsolutePath().endsWith(".iface.xml")) {
      file = new File(file.getAbsolutePath() + ".iface.xml");
    }
    
    workingFile = file;
    return true;
  }
  
  public Graphics2D newG() {
    return FontUtil.prepare(getGraphics());
  }
  
  private class Popup extends JPopupMenu {
    final Map<CommonField, JMenuItem> items = new HashMap<>();
    
    public Popup() {
      for (final CommonField cf : CommonField.values()) {
        JMenuItem mi = new JMenuItem(cf.menuName);
        add(mi);
        items.put(cf, mi);
        mi.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            try {
              executeAction(cf);
            } catch (Exception e1) {
              throw new RuntimeException(e1);
            }
          }
        });
      }
    }
    
    private void executeAction(CommonField cf) throws Exception {
      List<Widget> selected = selected();
      if (selected.size() == 0) {
        PointInfo pi = getPointInfo(mouse);
        if (pi == null) return;
        Graphics2D g = newG();
        Object fieldValue = visio.getFieldValue(g, cf, pi.widget, mouse);
        g.dispose();
        
        fieldValue = changeCommonField(cf, fieldValue);
        if (fieldValue == null) return;
        g = newG();
        Act act = visio.setFieldValue(g, pi.widget, cf, fieldValue, mouse);
        g.dispose();
        if (act != null) actContainer.addAct(act);
        return;
      }
      {
        Object fieldValue = null;
        final Set<Widget> toChange = new HashSet<>();
        {
          boolean cleanFieldValue = false;
          Graphics2D g = newG();
          for (Widget widget : selected) {
            if (!visio.getChangableFields(g, widget, null).contains(cf)) continue;
            toChange.add(widget);
            
            Object x = visio.getFieldValue(g, cf, widget, null);
            if (fieldValue == null) {
              fieldValue = x;
              continue;
            }
            if (x == null) continue;
            if (x.equals(fieldValue)) continue;
            cleanFieldValue = true;
          }
          g.dispose();
          if (cleanFieldValue) fieldValue = null;
        }
        fieldValue = changeCommonField(cf, fieldValue);
        if (fieldValue == null) return;
        {
          Graphics2D g = newG();
          ActGroup a = new ActGroup();
          for (Widget widget : toChange) {
            a.group.add(visio.setFieldValue(g, widget, cf, fieldValue, null));
          }
          g.dispose();
          if (a.group.size() == 0) return;
          if (a.group.size() == 1) {
            actContainer.addAct(a.group.get(0));
          } else {
            actContainer.addAct(a);
          }
        }
      }
    }
    
    public void update() throws Exception {
      Graphics2D g = newG();
      Set<CommonField> enabled = getFields(g);
      g.dispose();
      for (Entry<CommonField, JMenuItem> e : items.entrySet()) {
        e.getValue().setEnabled(enabled.contains(e.getKey()));
      }
    }
    
    private Set<CommonField> getFields(Graphics2D g) throws Exception {
      List<Widget> list = selected();
      if (list.size() == 0) {
        PointInfo pi = getPointInfo(mouse);
        if (pi == null) return Collections.emptySet();
        return visio.getChangableFields(g, pi.widget, mouse);
      }
      {
        Set<CommonField> ret = new HashSet<>();
        for (Widget widget : list) {
          ret.addAll(visio.getChangableFields(g, widget, null));
        }
        return ret;
      }
    }
  }
  
  private final Popup popup = new Popup();
  
  private Object changeCommonField(CommonField cf, Object fieldValue) {
    switch (cf) {
    case COLOR:
      return changeFieldColor((Color)fieldValue);
      
    case FONT:
      return changeFieldFont((FontDot)fieldValue);
      
    default:
      throw new IllegalArgumentException("Unknown value cf = " + cf);
    }
  }
  
  private FontDot changeFieldFont(FontDot fontDot) {
    FontChooserDialog d = new FontChooserDialog(this);
    d.show(fontDot.family, fontDot.size);
    if (d.selectedFontFamily == null) return null;
    return new FontDot(d.selectedFontFamily, d.selectedSize);
  }
  
  private Color changeFieldColor(Color color) {
    return JColorChooser.showDialog(this, "Выберите цвет", color);
  }
}
